package repository

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"

	"mall/common/enumx"
	"mall/common/modelx"
	"mall/service/forum/model"

	"github.com/volatiletech/sqlboiler/v4/boil"
	"github.com/volatiletech/sqlboiler/v4/queries"
	"github.com/volatiletech/sqlboiler/v4/queries/qm"
	"github.com/zeromicro/go-zero/core/stores/cache"
)

type (
	// ForumPostRepository is an interface to be customized, add more methods here,
	// and implement the added methods in customForumPostRepository.
	ForumPostRepository interface {
		Conn() error
		FindOne(
			ctx context.Context,
			id int64,
			asPreview bool,
			cols []string,
		) (*model.ForumPost, error) /////
		GetList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ForumPost, int64, error) /////
		GetObjectList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ForumPost, int64, error) /////
		GetTopicList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ForumPost, int64, error) //////
		Insert(
			ctx context.Context,
			post *model.ForumPost,
			meta []*model.ForumPostMetum,
			extra *model.ForumExtra,
		) error
		Update(
			ctx context.Context,
			post *model.ForumPost,
			meta []*model.ForumPostMetum,
			extra *model.ForumExtra,
		) (int64, error)
		UpdateBatch(
			ctx context.Context,
			cols model.M,
			filter map[string]map[string][]any,
		) (int64, error)
		BackendUpdateBatch(
			ctx context.Context,
			ids []uint64,
			cols model.M,
			extra *model.ForumExtra,
		) (int64, error) //////
		ForceDeleteBatch(ctx context.Context, ids []int64) (int64, error)
		GetMeta(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ForumPostMetum, int64, error)
		IncreaseMeta(ctx context.Context, ids []int64, metaKey string, step int) (int64, error) //////
		DecreaseMeta(ctx context.Context, ids []int64, metaKey string, step int) (int64, error) //////
		GetMedia(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ForumAttachmentRelationship, int64, error)
		BackendUpdateMediaBatch(
			ctx context.Context,
			ids []uint64,
			cols model.M,
		) (int64, error)
		GetMediaCount(
			ctx context.Context,
			filter map[string]map[string][]any,
		) (int64, error)
		SyncQiniu(ctx context.Context, id uint64, mediaId uint64, url string) (int64, error) //deprecated
	}

	customForumPostRepository struct {
		cacheConf  cache.CacheConf
		dataSource string

		mysqlConn *sql.DB
	}

	// Custom struct for selecting a subset of data
	RandomInfo struct {
		MaxId      int     `boil:"max_id"`
		MinId      int     `boil:"min_id"`
		Rand       float64 `boil:"rand"`
		PhotoCount int     `boil:"photo_count_sum"` //
		TopicCount int     `boil:"topic_count"`
	}
)

// NewForumPostRepository returns a repository for the database table.
func NewForumPostRepository(dataSource string, cacheConf cache.CacheConf) ForumPostRepository {
	return &customForumPostRepository{
		cacheConf:  cacheConf,
		dataSource: dataSource,
	}
}

func (r *customForumPostRepository) Conn() error {
	if r.mysqlConn == nil {
		mysqlConn, err := modelx.NewMysqlConn(r.dataSource)
		if err != nil {
			return err
		}

		r.mysqlConn = mysqlConn
	}

	return nil
}

func (r *customForumPostRepository) FindOne(
	ctx context.Context,
	id int64,
	asPreview bool,
	cols []string,
) (post *model.ForumPost, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	post, err = model.FindForumPost(ctx, r.mysqlConn, uint64(id), cols...)
	if err != nil {
		return
	}

	if asPreview || post.PostStatus == uint8(enumx.ObjectStatusAproved) {
		return
	}

	return nil, nil
}

func (r *customForumPostRepository) GetList(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ForumPost, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var offset int
	var limit int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "offset":
			if val, ok := v.(int32); ok && val > 0 {
				offset = int(val)
			}
		case "limit":
			if val, ok := v.(int32); ok && val > 0 {
				limit = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ForumPostColumns.PostID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostID, opMap)...)
			case model.ForumPostColumns.ForumID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ForumID, opMap)...)
			case model.ForumPostColumns.TopicID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.TopicID, opMap)...)
			case model.ForumPostColumns.ParentID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ParentID, opMap)...)
			case model.ForumPostColumns.PostAuthorID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorID, opMap)...)
			case model.ForumPostColumns.PostAuthorNickname:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorNickname, opMap)...)
			case model.ForumPostColumns.PostAuthorEmail:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorEmail, opMap)...)
			case model.ForumPostColumns.PostAuthorIP:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorIP, opMap)...)
			case model.ForumPostColumns.PostAuthorRankID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRankID, opMap)...)
			case model.ForumPostColumns.PostAuthorRoleID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRoleID, opMap)...)
			case model.ForumPostColumns.PostAuthorAnonymous:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorAnonymous, opMap)...)
			case model.ForumPostColumns.PostTitle:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostTitle, opMap)...)
			case model.ForumPostColumns.PostStatus:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostStatus, opMap)...)
			case model.ForumPostColumns.Sticky:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.Sticky, opMap)...)
			case model.ForumPostColumns.CommentStatus:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentStatus, opMap)...)
			case model.ForumPostColumns.CommentCount:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentCount, opMap)...)
			case model.ForumPostColumns.PhotoCount:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PhotoCount, opMap)...)
			case model.ForumPostColumns.AverageRating:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.AverageRating, opMap)...)
			case model.ForumPostColumns.PostDate:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostDate, opMap)...)
			case model.ForumPostColumns.PostDateGMT:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostDateGMT, opMap)...)
			case model.ForumPostColumns.PostModifiedDate:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostModifiedDate, opMap)...)
			case model.ForumPostColumns.PostModifiedGMT:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostModifiedGMT, opMap)...)
			case model.ForumPostColumns.DeletedAt:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.DeletedAt, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		if withCount >= 2 {
			var nextCount int64
			var previousCount int64
			subMods := make([]qm.QueryMod, len(mods), len(mods)+3)
			copy(subMods, mods)
			subMods = append(subMods, qm.Limit(1))
			subMods = append(subMods, qm.Select(model.ForumPostColumns.PostID))

			if withCount != 3 && offset >= 1 {
				subMods = append(subMods, qm.Offset(offset-1))
				previousList, previousErr := model.ForumPosts(subMods...).All(ctx, r.mysqlConn)
				if previousErr != nil {
					err = previousErr
					return
				}
				if len(previousList) > 0 {
					previousCount = 1
				}
			}
			if limit > 0 {
				subMods = append(subMods, qm.Offset(offset+limit))
				nextList, nextErr := model.ForumPosts(subMods...).All(ctx, r.mysqlConn)
				if nextErr != nil {
					err = nextErr
					return
				}
				if len(nextList) > 0 {
					nextCount = 1
				}
			}

			count = previousCount + nextCount*2
		} else {
			count, err = model.ForumPosts(mods...).Count(ctx, r.mysqlConn)
			if err != nil {
				if err == sql.ErrNoRows {
					err = nil
					return
				}

				return
			}

			if withCount == 0 {
				return
			}
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	} else if limit > 0 || offset > 0 {
		if limit > 0 {
			mods = append(mods, qm.Limit(limit))
		}
		if offset > 0 {
			mods = append(mods, qm.Offset(offset))
		}
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ForumPostColumns.PostID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ForumPosts(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customForumPostRepository) GetObjectList(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ForumPost, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var offset int
	var limit int
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "offset":
			if val, ok := v.(int32); ok && val > 0 {
				offset = int(val)
			}
		case "limit":
			if val, ok := v.(int32); ok && val > 0 {
				limit = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "asPreview":
			if val, ok := v.(uint64); ok && val > 0 {
				mods = append(mods, qm.Where(fmt.Sprintf(
					"(%s = ? OR %s = ?)",
					model.ForumPostColumns.PostAuthorID,
					model.ForumPostColumns.PostStatus), val, uint8(enumx.ObjectStatusAproved)))
			} else { // 不是预览，必须是已发布
				mods = append(mods, model.ForumPostWhere.PostStatus.EQ(uint8(enumx.ObjectStatusAproved)))
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ForumPostColumns.PostID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostID, opMap)...)
			case model.ForumPostColumns.ForumID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ForumID, opMap)...)
			case model.ForumPostColumns.TopicID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.TopicID, opMap)...)
			case model.ForumPostColumns.ParentID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ParentID, opMap)...)
			case model.ForumPostColumns.PostAuthorID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorID, opMap)...)
			case model.ForumPostColumns.PostAuthorNickname:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorNickname, opMap)...)
			case model.ForumPostColumns.PostAuthorEmail:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorEmail, opMap)...)
			case model.ForumPostColumns.PostAuthorIP:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorIP, opMap)...)
			case model.ForumPostColumns.PostAuthorRankID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRankID, opMap)...)
			case model.ForumPostColumns.PostAuthorRoleID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRoleID, opMap)...)
			case model.ForumPostColumns.PostAuthorAnonymous:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorAnonymous, opMap)...)
			case model.ForumPostColumns.PostTitle:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostTitle, opMap)...)
			case model.ForumPostColumns.PostStatus:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostStatus, opMap)...)
			case model.ForumPostColumns.Sticky:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.Sticky, opMap)...)
			case model.ForumPostColumns.CommentStatus:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentStatus, opMap)...)
			case model.ForumPostColumns.CommentCount:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentCount, opMap)...)
			case model.ForumPostColumns.PhotoCount:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PhotoCount, opMap)...)
			case model.ForumPostColumns.AverageRating:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.AverageRating, opMap)...)
			case model.ForumPostColumns.PostDate:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostDate, opMap)...)
			case model.ForumPostColumns.PostDateGMT:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostDateGMT, opMap)...)
			case model.ForumPostColumns.PostModifiedDate:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostModifiedDate, opMap)...)
			case model.ForumPostColumns.PostModifiedGMT:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostModifiedGMT, opMap)...)
			case model.ForumPostColumns.DeletedAt:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.DeletedAt, opMap)...)
			}
		}
	}
	mods = append(mods, qm.Distinct(model.ForumPostColumns.ParentID))

	if withCount >= 0 {
		if withCount >= 2 {
			var nextCount int64
			var previousCount int64
			subMods := make([]qm.QueryMod, len(mods), len(mods)+3)
			copy(subMods, mods)
			subMods = append(subMods, qm.Limit(1))
			subMods = append(subMods, qm.Select(model.ForumPostColumns.ParentID))

			if withCount != 3 && offset >= 1 {
				subMods = append(subMods, qm.Offset(offset-1))
				previousList, previousErr := model.ForumPosts(subMods...).All(ctx, r.mysqlConn)
				if previousErr != nil {
					err = previousErr
					return
				}
				if len(previousList) > 0 {
					previousCount = 1
				}
			}
			if limit > 0 {
				subMods = append(subMods, qm.Offset(offset+limit))
				nextList, nextErr := model.ForumPosts(subMods...).All(ctx, r.mysqlConn)
				if nextErr != nil {
					err = nextErr
					return
				}
				if len(nextList) > 0 {
					nextCount = 1
				}
			}

			count = previousCount + nextCount*2
		} else {
			count, err = model.ForumPosts(mods...).Count(ctx, r.mysqlConn)
			if err != nil {
				return
			}

			if withCount == 0 {
				return
			}
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	} else if limit > 0 || offset > 0 {
		if limit > 0 {
			mods = append(mods, qm.Limit(limit))
		}
		if offset > 0 {
			mods = append(mods, qm.Offset(offset))
		}
	}

	mods = append(mods, qm.Select(model.ForumPostColumns.ParentID))
	list, err = model.ForumPosts(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customForumPostRepository) GetTopicList(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ForumPost, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var offset int
	var limit int
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "offset":
			if val, ok := v.(int32); ok && val > 0 {
				offset = int(val)
			}
		case "limit":
			if val, ok := v.(int32); ok && val > 0 {
				limit = int(val)
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ForumPostColumns.PostID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostID, opMap)...)
			case model.ForumPostColumns.ForumID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ForumID, opMap)...)
			case model.ForumPostColumns.TopicID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.TopicID, opMap)...)
			case model.ForumPostColumns.ParentID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ParentID, opMap)...)
			case model.ForumPostColumns.PostAuthorID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorID, opMap)...)
			case model.ForumPostColumns.PostAuthorNickname:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorNickname, opMap)...)
			case model.ForumPostColumns.PostAuthorEmail:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorEmail, opMap)...)
			case model.ForumPostColumns.PostAuthorIP:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorIP, opMap)...)
			case model.ForumPostColumns.PostAuthorRankID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRankID, opMap)...)
			case model.ForumPostColumns.PostAuthorRoleID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRoleID, opMap)...)
			case model.ForumPostColumns.PostAuthorAnonymous:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorAnonymous, opMap)...)
			case model.ForumPostColumns.PostTitle:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostTitle, opMap)...)
			case model.ForumPostColumns.PostStatus:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostStatus, opMap)...)
			case model.ForumPostColumns.Sticky:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.Sticky, opMap)...)
			case model.ForumPostColumns.CommentStatus:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentStatus, opMap)...)
			case model.ForumPostColumns.CommentCount:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentCount, opMap)...)
			case model.ForumPostColumns.PhotoCount:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PhotoCount, opMap)...)
			case model.ForumPostColumns.AverageRating:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.AverageRating, opMap)...)
			case model.ForumPostColumns.PostDate:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostDate, opMap)...)
			case model.ForumPostColumns.PostDateGMT:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostDateGMT, opMap)...)
			case model.ForumPostColumns.PostModifiedDate:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostModifiedDate, opMap)...)
			case model.ForumPostColumns.PostModifiedGMT:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostModifiedGMT, opMap)...)
			case model.ForumPostColumns.DeletedAt:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.DeletedAt, opMap)...)
			}
		}
	}

	mods = append(mods, qm.Select(fmt.Sprintf(
		"max(%s) as %s, %s",
		model.ForumPostColumns.PostID,
		model.ForumPostColumns.PostID,
		model.ForumPostColumns.TopicID,
	)))
	mods = append(mods, qm.GroupBy(model.ForumPostColumns.TopicID))
	mods = append(mods, qm.OrderBy(model.ForumPostColumns.PostID+" DESC"))

	if withCount >= 0 {
		if withCount >= 2 {
			var nextCount int64
			var previousCount int64
			subMods := make([]qm.QueryMod, len(mods), len(mods)+2)
			copy(subMods, mods)
			subMods = append(subMods, qm.Limit(1))

			if withCount != 3 && offset >= 1 {
				subMods = append(subMods, qm.Offset(offset-1))
				previousList, previousErr := model.ForumPosts(subMods...).All(ctx, r.mysqlConn)
				if previousErr != nil {
					err = previousErr
					return
				}

				if len(previousList) > 0 {
					previousCount = 1
				}
			}
			if limit > 0 {
				subMods = append(subMods, qm.Offset(offset+limit))
				nextList, nextErr := model.ForumPosts(subMods...).All(ctx, r.mysqlConn)
				if nextErr != nil {
					err = nextErr
					return
				}

				if len(nextList) > 0 {
					nextCount = 1
				}
			}

			count = previousCount + nextCount*2
		} else {
			subMods := make([]qm.QueryMod, len(mods), len(mods)+2)
			copy(subMods, mods)
			subMods = append(subMods, qm.Select(fmt.Sprintf("count(%s) as topic_count", model.ForumPostColumns.TopicID)))
			subMods = append(subMods, qm.From(model.TableNames.ForumPosts+" as posts"))
			var info RandomInfo
			// Use query building
			err = model.NewQuery(subMods...).Bind(ctx, r.mysqlConn, &info)
			if err != nil {
				if err == sql.ErrNoRows {
					err = nil
					return
				}

				return
			}

			count = int64(info.TopicCount)
			if withCount == 0 {
				return
			}
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	} else if limit > 0 || offset > 0 {
		if limit > 0 {
			mods = append(mods, qm.Limit(limit))
		}
		if offset > 0 {
			mods = append(mods, qm.Offset(offset))
		}
	}

	list, err = model.ForumPosts(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customForumPostRepository) Insert(
	ctx context.Context,
	post *model.ForumPost,
	meta []*model.ForumPostMetum,
	extra *model.ForumExtra,
) error {
	if err := r.Conn(); err != nil {
		return err
	}

	tx, err := r.mysqlConn.BeginTx(ctx, nil)
	if err != nil {
		return err
	}

	err = post.Insert(ctx, tx, boil.Infer())
	if err != nil {
		tx.Rollback()
		return err
	}

	pid := post.PostID
	if len(meta) > 0 {
		for _, m := range meta {
			m.PostID = pid
			err = m.Insert(ctx, tx, boil.Infer())
			if err != nil {
				tx.Rollback()
				return err
			}
		}
	}

	if len(extra.Media) > 0 {
		for _, m := range extra.Media {
			m.ObjectID = pid
			m.ObjectType = uint16(enumx.ObjectTypePost)
			err = m.Insert(ctx, tx, boil.Infer())
			if err != nil {
				tx.Rollback()
				return err
			}
		}
	}

	tx.Commit()
	return nil
}

func (r *customForumPostRepository) Update(
	ctx context.Context,
	post *model.ForumPost,
	meta []*model.ForumPostMetum,
	extra *model.ForumExtra,
) (int64, error) {
	if err := r.Conn(); err != nil {
		return 0, err
	}

	tx, err := r.mysqlConn.BeginTx(ctx, nil)
	if err != nil {
		return 0, err
	}

	postId, err := post.Update(ctx, tx, boil.Blacklist(
		model.ForumPostColumns.PostID,
		model.ForumPostColumns.ForumID,
		model.ForumPostColumns.TopicID,
		model.ForumPostColumns.ParentID,
		model.ForumPostColumns.PostAuthorID,
		model.ForumPostColumns.PostAuthorNickname,
		model.ForumPostColumns.PostAuthorEmail,
		model.ForumPostColumns.PostAuthorAvatar,
		model.ForumPostColumns.PostAuthorIP,
		model.ForumPostColumns.PostDate,
		model.ForumPostColumns.PostDateGMT,
		model.ForumPostColumns.CommentCount,
	))
	if err != nil {
		tx.Rollback()
		return 0, err
	}

	pid := post.PostID
	if len(meta) > 0 {
		for _, m := range meta {
			if m.MetaID > 0 {
				_, err = m.Update(ctx, tx, boil.Blacklist(
					model.ForumPostMetumColumns.MetaID,
					model.ForumPostMetumColumns.PostID,
				))
			} else {
				m.PostID = pid
				err = m.Insert(ctx, tx, boil.Infer())
			}
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
	}

	if len(extra.Media) > 0 {
		for _, m := range extra.Media {
			if m.AttachmentRelationshipID > 0 {
				_, err = m.Update(ctx, tx, boil.Blacklist(
					model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID,
					model.ForumAttachmentRelationshipColumns.ObjectID,
					model.ForumAttachmentRelationshipColumns.ObjectType,
					model.ForumAttachmentRelationshipColumns.AttachmentID,
				))
			} else {
				m.ObjectID = pid
				m.ObjectType = uint16(enumx.ObjectTypePost)
				err = m.Insert(ctx, tx, boil.Infer())
			}
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
	}
	if len(extra.DelMetaIds) > 0 {
		ids := make([]any, 0, len(extra.DelMetaIds))
		for _, id := range extra.DelMetaIds {
			ids = append(ids, id)
		}
		var mods []qm.QueryMod
		mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ForumPostMetumColumns.MetaID), ids...))
		_, err := model.ForumPostMeta(mods...).DeleteAll(ctx, tx)
		if err != nil {
			tx.Rollback()
			return 0, err
		}
	}
	if len(extra.DelMediaIds) > 0 {
		ids := make([]any, 0, len(extra.DelMediaIds))
		for _, id := range extra.DelMediaIds {
			ids = append(ids, id)
		}
		var mods []qm.QueryMod
		mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID), ids...))
		_, err := model.ForumAttachmentRelationships(mods...).DeleteAll(ctx, tx)
		if err != nil {
			tx.Rollback()
			return 0, err
		}
	}

	tx.Commit()
	return postId, nil
}

func (r *customForumPostRepository) UpdateBatch(
	ctx context.Context,
	cols model.M,
	filter map[string]map[string][]any,
) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ForumPostColumns.PostID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostID, opMap)...)
			case model.ForumPostColumns.ForumID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ForumID, opMap)...)
			case model.ForumPostColumns.TopicID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.TopicID, opMap)...)
			case model.ForumPostColumns.ParentID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ParentID, opMap)...)
			case model.ForumPostColumns.PostAuthorID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorID, opMap)...)
			case model.ForumPostColumns.PostAuthorNickname:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorNickname, opMap)...)
			case model.ForumPostColumns.PostAuthorEmail:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorEmail, opMap)...)
			case model.ForumPostColumns.PostAuthorIP:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorIP, opMap)...)
			case model.ForumPostColumns.PostAuthorRankID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRankID, opMap)...)
			case model.ForumPostColumns.PostAuthorRoleID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRoleID, opMap)...)
			case model.ForumPostColumns.PostAuthorAnonymous:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorAnonymous, opMap)...)
			case model.ForumPostColumns.PostTitle:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostTitle, opMap)...)
			case model.ForumPostColumns.PostStatus:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostStatus, opMap)...)
			case model.ForumPostColumns.Sticky:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.Sticky, opMap)...)
			case model.ForumPostColumns.CommentStatus:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentStatus, opMap)...)
			case model.ForumPostColumns.CommentCount:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentCount, opMap)...)
			case model.ForumPostColumns.PhotoCount:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PhotoCount, opMap)...)
			case model.ForumPostColumns.AverageRating:
				mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.AverageRating, opMap)...)
			case model.ForumPostColumns.PostDate:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostDate, opMap)...)
			case model.ForumPostColumns.PostDateGMT:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostDateGMT, opMap)...)
			case model.ForumPostColumns.PostModifiedDate:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostModifiedDate, opMap)...)
			case model.ForumPostColumns.PostModifiedGMT:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.PostModifiedGMT, opMap)...)
			case model.ForumPostColumns.DeletedAt:
				mods = append(mods, modelx.GetTimeMods(model.ForumPostColumns.DeletedAt, opMap)...)
			}
		}
	}
	if len(mods) == 0 {
		return
	}

	rowsAff, err = model.ForumPosts(mods...).UpdateAll(ctx, r.mysqlConn, cols)
	return
}

func (r *customForumPostRepository) BackendUpdateBatch(
	ctx context.Context,
	ids []uint64,
	cols model.M,
	extra *model.ForumExtra,
) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	if len(ids) > 0 {
		tx, txErr := r.mysqlConn.BeginTx(ctx, nil)
		if txErr != nil {
			err = txErr
			return
		}

		var mods []qm.QueryMod
		mods = append(mods, model.ForumPostWhere.PostID.IN(ids))
		rowsAff, err = model.ForumPosts(mods...).UpdateAll(ctx, tx, cols)
		if err != nil {
			tx.Rollback()
			return 0, err
		}

		if len(extra.Logs) > 0 {
			for _, log := range extra.Logs {
				err = log.Insert(ctx, tx, boil.Infer())
				if err != nil {
					tx.Rollback()
					return 0, err
				}
			}
		}

		if len(extra.Notices) > 0 {
			for _, notice := range extra.Notices {
				err = notice.Insert(ctx, tx, boil.Infer())
				if err != nil {
					tx.Rollback()
					return 0, err
				}
			}
		}

		tx.Commit()
		return
	}

	return
}

func (r *customForumPostRepository) ForceDeleteBatch(ctx context.Context, ids []int64) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	if len(ids) > 0 {
		values := make([]any, 0, len(ids))
		for _, id := range ids {
			values = append(values, uint64(id))
		}

		mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ForumPostColumns.PostID), values...))
	}

	rowsAff, err = model.ForumPosts(mods...).DeleteAll(ctx, r.mysqlConn)
	return
}

func (r *customForumPostRepository) GetMeta(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ForumPostMetum, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ForumPostMetumColumns.MetaID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostMetumColumns.MetaID, opMap)...)
			case model.ForumPostMetumColumns.PostID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostMetumColumns.PostID, opMap)...)
			case model.ForumPostMetumColumns.MetaKey:
				mods = append(mods, modelx.GetStringMods(model.ForumPostMetumColumns.MetaKey, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ForumPostMeta(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ForumPostMetumColumns.MetaID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ForumPostMeta(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customForumPostRepository) IncreaseMeta(
	ctx context.Context,
	ids []int64,
	metaKey string,
	step int,
) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	if len(ids) > 0 {
		pids := make([]string, 0, len(ids))
		for _, id := range ids {
			pids = append(pids, strconv.FormatInt(id, 10))
		}
		var info RandomInfo
		err = queries.Raw(fmt.Sprintf(
			`update %s set %s=%s+%d where %s="%s" and %s in (%s)`,
			model.TableNames.ForumPostMeta,
			model.ForumPostMetumColumns.MetaValue,
			model.ForumPostMetumColumns.MetaValue,
			step,
			model.ForumPostMetumColumns.MetaKey,
			metaKey,
			model.ForumPostMetumColumns.PostID,
			strings.Join(pids, ","),
		)).Bind(ctx, r.mysqlConn, &info)

		if err == sql.ErrNoRows {
			return 0, model.ErrNotFound
		}
	}

	return
}

func (r *customForumPostRepository) DecreaseMeta(
	ctx context.Context,
	ids []int64,
	metaKey string,
	step int,
) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	if len(ids) > 0 {
		pids := make([]string, 0, len(ids))
		for _, id := range ids {
			pids = append(pids, strconv.FormatInt(id, 10))
		}
		var info RandomInfo
		err = queries.Raw(fmt.Sprintf(
			`update %s set %s=%s-%d where %s="%s" and %s in (%s)`,
			model.TableNames.ForumPostMeta,
			model.ForumPostMetumColumns.MetaValue,
			model.ForumPostMetumColumns.MetaValue,
			step,
			model.ForumPostMetumColumns.MetaKey,
			metaKey,
			model.ForumPostMetumColumns.PostID,
			strings.Join(pids, ","),
		)).Bind(ctx, r.mysqlConn, &info)

		if err == sql.ErrNoRows {
			return 0, model.ErrNotFound
		}
	}

	return
}

func (r *customForumPostRepository) GetMedia(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ForumAttachmentRelationship, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID:
				mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID, opMap)...)
			case model.ForumAttachmentRelationshipColumns.ObjectID:
				mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.ObjectID, opMap)...)
			case model.ForumAttachmentRelationshipColumns.ObjectType:
				mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.ObjectType, opMap)...)
			case model.ForumAttachmentRelationshipColumns.AttachmentID:
				mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.AttachmentID, opMap)...)
			case model.ForumAttachmentRelationshipColumns.AttachmentThumbnail:
				mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.AttachmentThumbnail, opMap)...)
			case model.ForumAttachmentRelationshipColumns.AttachmentOrder:
				mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.AttachmentOrder, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ForumAttachmentRelationships(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID)
		columns = append(columns, model.ForumAttachmentRelationshipColumns.ObjectID)
		columns = append(columns, model.ForumAttachmentRelationshipColumns.AttachmentThumbnail)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ForumAttachmentRelationships(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customForumPostRepository) BackendUpdateMediaBatch(
	ctx context.Context,
	ids []uint64,
	cols model.M,
) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	if len(ids) > 0 {
		var mods []qm.QueryMod
		mods = append(mods, model.ForumAttachmentRelationshipWhere.AttachmentRelationshipID.IN(ids))
		rowsAff, err = model.ForumAttachmentRelationships(mods...).UpdateAll(ctx, r.mysqlConn, cols)
	}

	return
}

func (r *customForumPostRepository) GetMediaCount(
	ctx context.Context,
	filter map[string]map[string][]any,
) (count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ForumPostColumns.ForumID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ForumID, opMap)...)
			case model.ForumPostColumns.PostAuthorID:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorID, opMap)...)
			case model.ForumPostColumns.PostStatus:
				mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostStatus, opMap)...)
			}
		}
	}

	mods = append(mods, qm.Select(fmt.Sprintf("sum(%s) as photo_count_sum", model.ForumPostColumns.PhotoCount)))
	mods = append(mods, qm.From(model.TableNames.ForumPosts+" as posts"))

	var info RandomInfo
	// Use query building
	err = model.NewQuery(mods...).Bind(ctx, r.mysqlConn, &info)
	if err != nil {
		return
	}

	return int64(info.PhotoCount), nil
}

func (r *customForumPostRepository) SyncQiniu(ctx context.Context, id uint64, mediaId uint64, url string) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	mods = append(mods, model.ForumAttachmentRelationshipWhere.ObjectID.EQ(id))
	mods = append(mods, model.ForumAttachmentRelationshipWhere.AttachmentID.EQ(mediaId))
	rowsAff, err = model.ForumAttachmentRelationships(mods...).UpdateAll(ctx, r.mysqlConn, model.M{
		model.ForumAttachmentRelationshipColumns.AttachmentImageURL: url,
	})

	return
}
